/// An SQL request that can decode database rows.
///
/// `SQLRequest` allows you to safely embed raw values in your SQL,
/// without any risk of syntax errors or SQL injection:
///
/// ```swift
/// extension Player: FetchableRecord {
///     static func filter(name: String) -> SQLRequest<Player> {
///         "SELECT * FROM player WHERE name = \(name)"
///     }
///
///     static func maximumScore() -> SQLRequest<Int> {
///         "SELECT MAX(score) FROM player"
///     }
/// }
///
/// try dbQueue.read { db in
///     let players = try Player.filter(name: "O'Brien").fetchAll(db) // [Player]
///     let maxScore = try Player.maximumScore().fetchOne(db)         // Int?
/// }
/// ```
///
/// An `SQLRequest` can be created from a string literal or interpolation, as in
/// the above examples, and from the initializers documented below.
///
/// ## Topics
///
/// ### Creating an SQL Request from a Literal Value
///
/// - ``init(stringLiteral:)``
/// - ``init(unicodeScalarLiteral:)-84mq8``
/// - ``init(extendedGraphemeClusterLiteral:)-1sf75``
///
/// ### Creating an SQL Request from an Interpolation
///
/// - ``init(stringInterpolation:)``
///
/// ### Creating an SQL Request from an SQL Literal
///
/// - ``init(literal:adapter:cached:)-4vuxn``
/// - ``init(literal:adapter:cached:)-82f97``
///
/// ### Creating an SQL Request from an SQL String
///
/// - ``init(sql:arguments:adapter:cached:)-3qq8t``
/// - ``init(sql:arguments:adapter:cached:)-5ecx2``
public struct SQLRequest<RowDecoder> {
    /// There are two statement caches: one "public" for statements generated by
    /// the user, and one "internal" for the statements generated by GRDB. Those
    /// are separated so that GRDB has no opportunity to inadvertently modify
    /// the arguments of user's cached statements.
    enum Cache {
        /// The public cache, for library user
        case `public`
        
        /// The internal cache, for GRDB
        case `internal`
    }
    
    /// The row adapter.
    public var adapter: (any RowAdapter)?
    
    private(set) var sqlLiteral: SQL
    let cache: Cache?
    
    private init(
        literal sqlLiteral: SQL,
        adapter: (any RowAdapter)?,
        fromCache cache: Cache?,
        type: RowDecoder.Type)
    {
        self.sqlLiteral = sqlLiteral
        self.adapter = adapter
        self.cache = cache
    }
}

extension SQLRequest {
    /// Creates a request from an SQL string.
    ///
    /// ```swift
    /// let request = SQLRequest<String>(sql: """
    ///     SELECT name FROM player
    ///     """)
    /// let request = SQLRequest<Player>(sql: """
    ///     SELECT * FROM player WHERE name = ?
    ///     """, arguments: ["O'Brien"])
    /// ```
    ///
    /// - parameters:
    ///     - sql: An SQL string.
    ///     - arguments: Statement arguments.
    ///     - adapter: Optional RowAdapter.
    ///     - cached: Defaults to false. If true, the request reuses a cached
    ///       prepared statement.
    public init(
        sql: String,
        arguments: StatementArguments = StatementArguments(),
        adapter: (any RowAdapter)? = nil,
        cached: Bool = false)
    {
        self.init(
            literal: SQL(sql: sql, arguments: arguments),
            adapter: adapter,
            fromCache: cached ? .public : nil,
            type: RowDecoder.self)
    }
    
    /// Creates a request from an ``SQL`` literal.
    ///
    /// ``SQL`` literals allow you to safely embed raw values in your SQL,
    /// without any risk of syntax errors or SQL injection:
    ///
    /// ```swift
    /// let name = "O'Brien"
    /// let request = SQLRequest<Player>(literal: """
    ///     SELECT * FROM player WHERE name = \(name)
    ///     """)
    /// ```
    ///
    /// - parameters:
    ///     - sqlLiteral: An `SQL` literal.
    ///     - adapter: Optional RowAdapter.
    ///     - cached: Defaults to false. If true, the request reuses a cached
    ///       prepared statement.
    public init(literal sqlLiteral: SQL, adapter: (any RowAdapter)? = nil, cached: Bool = false) {
        self.init(
            literal: sqlLiteral,
            adapter: adapter,
            fromCache: cached ? .public : nil,
            type: RowDecoder.self)
    }
}

extension SQLRequest<Row> {
    /// Creates a request of database rows, from an SQL string.
    ///
    /// ```swift
    /// let request = SQLRequest(sql: """
    ///     SELECT * FROM player WHERE name = ?
    ///     """, arguments: ["O'Brien"])
    /// ```
    ///
    /// - parameters:
    ///     - sql: An SQL string.
    ///     - arguments: Statement arguments.
    ///     - adapter: Optional RowAdapter.
    ///     - cached: Defaults to false. If true, the request reuses a cached
    ///       prepared statement.
    public init(
        sql: String,
        arguments: StatementArguments = StatementArguments(),
        adapter: (any RowAdapter)? = nil,
        cached: Bool = false)
    {
        self.init(
            literal: SQL(sql: sql, arguments: arguments),
            adapter: adapter,
            fromCache: cached ? .public : nil,
            type: Row.self)
    }
    
    /// Creates a request of database rows, from an ``SQL`` literal.
    ///
    /// ``SQL`` literals allow you to safely embed raw values in your SQL,
    /// without any risk of syntax errors or SQL injection:
    ///
    /// ```swift
    /// let name = "O'Brien"
    /// let request = SQLRequest(literal: """
    ///     SELECT * FROM player WHERE name = \(name)
    ///     """)
    /// ```
    ///
    /// - parameters:
    ///     - sqlLiteral: An `SQL` literal.
    ///     - adapter: Optional RowAdapter.
    ///     - cached: Defaults to false. If true, the request reuses a cached
    ///       prepared statement.
    public init(literal sqlLiteral: SQL, adapter: (any RowAdapter)? = nil, cached: Bool = false) {
        self.init(
            literal: sqlLiteral,
            adapter: adapter,
            fromCache: cached ? .public : nil,
            type: Row.self)
    }
}

extension SQLRequest: FetchRequest {
    public var sqlSubquery: SQLSubquery {
        .literal(sqlLiteral)
    }
    
    public func fetchCount(_ db: Database) throws -> Int {
        try SQLRequest<Int>("SELECT COUNT(*) FROM (\(self))").fetchOne(db)!
    }
    
    public func makePreparedRequest(
        _ db: Database,
        forSingleResult singleResult: Bool = false)
    throws -> PreparedRequest
    {
        let context = SQLGenerationContext(db)
        let sql = try sqlLiteral.sql(context)
        let statement: Statement
        switch cache {
        case .none:
            statement = try db.makeStatement(sql: sql)
        case .public:
            statement = try db.cachedStatement(sql: sql)
        case .internal:
            statement = try db.internalCachedStatement(sql: sql)
        }
        try statement.setArguments(context.arguments)
        return PreparedRequest(statement: statement, adapter: adapter)
    }
}

extension SQLRequest: ExpressibleByStringInterpolation {
    public init(unicodeScalarLiteral: String) {
        self.init(sql: unicodeScalarLiteral)
    }
    
    public init(extendedGraphemeClusterLiteral: String) {
        self.init(sql: extendedGraphemeClusterLiteral)
    }
    
    /// Creates an `SQLRequest` from the given literal SQL string.
    public init(stringLiteral: String) {
        self.init(sql: stringLiteral)
    }
    
    public init(stringInterpolation sqlInterpolation: SQLInterpolation) {
        self.init(literal: SQL(stringInterpolation: sqlInterpolation))
    }
}
